using System;
using System.ComponentModel;
using System.Data;
using System.Globalization;
using System.Linq.Expressions;
using TelerikMvcGridCustomBindingHelper.ORMIntegration;
using TelerikMvcGridCustomBindingHelper.TinyCache;
using TelerikMvcGridCustomBindingHelper.Util;

namespace TelerikMvcGridCustomBindingHelper.Mapper
{
    public interface IFluentMapperConfiguration
    {
        /// <summary>
        /// Indicates that the mappings are created as needed, if a mapping is not specified it will be 
        /// created automatically. Warning: COMPLEX TYPES (WITH NESTED PROPERTIES) CANNOT BE MAPPED IN THIS WAY, use manual mapping instead.
        /// </summary>
        IFluentMapperConfiguration CreateSimpleMappingsAsNeeded(bool createSimpleMappingsAsNeeded = true);

        IFluentMapperConfiguration AcceptNullValuesWhenFiltering(bool acceptNullValuesWhenFiltering = true);
        IFluentMapperConfiguration AcceptNullValuesWhenFiltering<TEntity>(bool acceptNullValuesWhenFiltering = true);
        IFluentMapperConfiguration UseCaseInsensitiveSearch(Action<IBaseCaseOptions> caseOptions);
        IFluentMapperConfiguration UseCaseInsensitiveSearch(bool useCaseInsensitiveSearchOnBothClientAndServerSide = true);
        IFluentMapperConfiguration DefaultSortMemberName(IDMemberName idMemberName, string memberNameOrPrefixOrSuffix, ListSortDirection sortDirection = ListSortDirection.Ascending);
        IFluentMapperConfiguration DefaultSortMemberName(IDMemberName idMemberName, ListSortDirection sortDirection);
        IFluentMapperConfiguration DefaultSortMemberName(IDMemberName idMemberName);
        IFluentMapperConfiguration DefaultSortMemberName(Func<Type, string> defaultSortMemberNameFunc, ListSortDirection sortDirection = ListSortDirection.Ascending);
        IFluentMapperConfiguration DefaultSortMemberName(string memberName, ListSortDirection sortDirection = ListSortDirection.Ascending);
        IFluentMapperConfiguration AddGlobalValueResolver<TValueResolver>() where TValueResolver : class, IGlobalValueResolver, new();
        IFluentMapperConfiguration AddGlobalValueResolver(IGlobalValueResolver globalValueResolver);
        IFluentMapperConfiguration SetGlobalBooleanSubstitute<TBooleanSubstitute>() where TBooleanSubstitute : class, IBooleanSubstitute, new();
        IFluentMapperConfiguration SetGlobalBooleanSubstitute(IBooleanSubstitute booleanSubstitute);
        IFluentMapperConfiguration SetGlobalNullSubstitute<TNullSubstitute>() where TNullSubstitute : class, INullSubstitute, new();
        IFluentMapperConfiguration SetGlobalNullSubstitute(INullSubstitute nullSubstitute);
        IFluentMapperConfiguration SetGlobalNullAlias<TNullAlias>() where TNullAlias : class, INullAlias, new();
        IFluentMapperConfiguration SetGlobalNullAlias(INullAlias nullAlias);
        IFluentMapperConfiguration SetCacheMechanism(ITinyCache<string> tinyCache);
        IFluentMapperConfiguration FallBackToLinqExpressionBuilder(bool shouldFallBackToLinqExpressionBuilder = true);
        IFluentMapperConfiguration SetDefaultCulture(CultureInfo cultureInfo);

        IFluentORMIntegrationConfiguration StartORMIntegration<TORMIntegration>()
            where TORMIntegration : class, IORMIntegration, new();

        IFluentORMIntegrationConfiguration StartORMIntegration<TORMIntegration>(Func<TORMIntegration> tormIntegrationFactory)
            where TORMIntegration : class, IORMIntegration;

        IFluentORMIntegrationConfiguration StartORMIntegration(IORMIntegration ormIntegration);
    }

    class FluentMapperConfiguration : IFluentMapperConfiguration
    {
        internal readonly ValueResolvers ValueResolvers;
        internal readonly BooleanSubstitutes BooleanSubstitutes;
        internal readonly NullSubstitutes NullSubstitutes;
        internal readonly NullAliases NullAliases;
        internal Func<Type, string> DefaultSortMemberNameFunc { get; private set; }
        internal IDMemberName DefaultSortMemberNameType { get; private set; }
        internal string DefaultSortMemberNameOrPrefixOrSuffix { get; private set; }
        internal ListSortDirection DefaultSortDirection { get; private set; }
        internal Func<IORMIntegration> ORMIntegrationFactory { get; private set; }
        internal ITinyCache<string> TinyCache { get; private set; }
        internal bool ShouldCreateSimpleMappingsAsNeeded { get; private set; }
        internal IBaseCaseOptions GlobalCaseInsensitiveSearchOptions { get; private set; }
        internal bool ShouldAcceptNullValuesWhenFiltering { get; private set; }
        internal AcceptNullValuesWhenFilteringType AcceptNullValuesWhenFilteringType { get; private set; }
        internal bool ShouldFallBackToLinqExpressionBuilder { get; private set; }
        internal CultureInfo DefaultCulture { get; private set; }

        public FluentMapperConfiguration()
        {
            DefaultSortMemberNameOrPrefixOrSuffix = "Id";
            DefaultSortMemberNameFunc = type => null;
            ValueResolvers = new ValueResolvers();
            BooleanSubstitutes = new BooleanSubstitutes();
            NullSubstitutes = new NullSubstitutes();
            NullAliases = new NullAliases();
            ORMIntegrationFactory = null;
            AcceptNullValuesWhenFilteringType = new AcceptNullValuesWhenFilteringType(null, false);
            // 3600000 Milliseconds = 60 Minutes
            // 900000 Milliseconds = 15 Minutes
            //                                     60 Min,  15 Min
            TinyCache = new TimedTinyCache<string>(3600000, 900000);
            ORMIntegrationHolder.TransactionBehavior = TransactionBehavior.None;
            ORMIntegrationHolder.TransactionIsolationLevel = IsolationLevel.Unspecified;
            DefaultCulture = CultureInfo.InvariantCulture;
        }

        /// <summary>
        /// Indicates that the mappings are created as needed, if a mapping is not specified it will be 
        /// created automatically. Warning: COMPLEX TYPES (WITH NESTED PROPERTIES) CANNOT BE MAPPED IN THIS WAY, use manual mapping instead.
        /// </summary>
        IFluentMapperConfiguration IFluentMapperConfiguration.CreateSimpleMappingsAsNeeded(bool createSimpleMappingsAsNeeded)
        {
            ShouldCreateSimpleMappingsAsNeeded = createSimpleMappingsAsNeeded;
            return this;
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.AcceptNullValuesWhenFiltering(bool acceptNullValuesWhenFiltering)
        {
            ShouldAcceptNullValuesWhenFiltering = acceptNullValuesWhenFiltering;
            return this;
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.AcceptNullValuesWhenFiltering<TEntity>(bool acceptNullValuesWhenFiltering)
        {
            AcceptNullValuesWhenFilteringType = new AcceptNullValuesWhenFilteringType(typeof(TEntity), acceptNullValuesWhenFiltering);
            return this;
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.UseCaseInsensitiveSearch(Action<IBaseCaseOptions> caseOptions)
        {
            GlobalCaseInsensitiveSearchOptions = new CaseInsensitive<object>();
            caseOptions(GlobalCaseInsensitiveSearchOptions);
            return this;
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.UseCaseInsensitiveSearch(bool useCaseInsensitiveSearchOnBothClientAndServerSide)
        {
            GlobalCaseInsensitiveSearchOptions = new CaseInsensitive<object>();
            GlobalCaseInsensitiveSearchOptions.BothClientAndServerSide(useCaseInsensitiveSearchOnBothClientAndServerSide);
            return this;
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.DefaultSortMemberName(IDMemberName idMemberName, string memberNameOrPrefixOrSuffix, ListSortDirection sortDirection)
        {
            switch (idMemberName)
            {
                case IDMemberName.Id:
                    break;
                case IDMemberName.EntityNamePlusId:
                    if (string.IsNullOrWhiteSpace(memberNameOrPrefixOrSuffix) == false)
                        throw new ArgumentException("EntityNamePlusId option does not supports MemberName or Prefix or Suffix.", "memberNameOrPrefixOrSuffix");
                    break;
                case IDMemberName.IdPlusEntityName:
                    if (string.IsNullOrWhiteSpace(memberNameOrPrefixOrSuffix) == false)
                        throw new ArgumentException("IdPlusEntityName option does not supports MemberName or Prefix or Suffix.", "memberNameOrPrefixOrSuffix");
                    break;
                case IDMemberName.PrefixPlusEntityName:
                    if (string.IsNullOrWhiteSpace(memberNameOrPrefixOrSuffix))
                        throw new ArgumentException("PrefixPlusEntityName option needs a valid Prefix.", "memberNameOrPrefixOrSuffix");
                    break;
                case IDMemberName.EntityNamePlusSuffix:
                    if (string.IsNullOrWhiteSpace(memberNameOrPrefixOrSuffix))
                        throw new ArgumentException("EntityNamePlusSuffix option needs a valid Suffix.", "memberNameOrPrefixOrSuffix");
                    break;
                case IDMemberName.FixedMemberName:
                    if (string.IsNullOrWhiteSpace(memberNameOrPrefixOrSuffix))
                        throw new ArgumentException("FixedMemberName option needs a valid MemberName.", "memberNameOrPrefixOrSuffix");
                    break;
                case IDMemberName.Guess:
                    if (string.IsNullOrWhiteSpace(memberNameOrPrefixOrSuffix) == false)
                        throw new ArgumentException("Guess option does not supports MemberName or Prefix or Suffix.", "memberNameOrPrefixOrSuffix");
                    break;
                default:
                    throw new ArgumentOutOfRangeException("idMemberName");
            }

            DefaultSortMemberNameType = idMemberName;
            DefaultSortMemberNameOrPrefixOrSuffix = memberNameOrPrefixOrSuffix;
            DefaultSortDirection = sortDirection;
            return this;
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.DefaultSortMemberName(IDMemberName idMemberName, ListSortDirection sortDirection)
        {
            return (this as IFluentMapperConfiguration).DefaultSortMemberName(idMemberName, null, sortDirection);
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.DefaultSortMemberName(IDMemberName idMemberName)
        {
            return (this as IFluentMapperConfiguration).DefaultSortMemberName(idMemberName, null);
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.DefaultSortMemberName(Func<Type, string> defaultSortMemberNameFunc, ListSortDirection sortDirection)
        {
            DefaultSortMemberNameFunc = defaultSortMemberNameFunc;
            DefaultSortDirection = sortDirection;
            return this;
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.DefaultSortMemberName(string memberName, ListSortDirection sortDirection)
        {
            if (string.IsNullOrWhiteSpace(memberName))
                throw new ArgumentException("Invalid MemberName.", "memberName");

            DefaultSortMemberNameType = IDMemberName.FixedMemberName;
            DefaultSortMemberNameOrPrefixOrSuffix = memberName;
            DefaultSortDirection = sortDirection;
            return this;
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.AddGlobalValueResolver<TValueResolver>()
        {
            return (this as IFluentMapperConfiguration).AddGlobalValueResolver(ObjectFactory<TValueResolver>.Create());
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.AddGlobalValueResolver(IGlobalValueResolver globalValueResolver)
        {
            new MappingExpression<object, object>(null).AddGlobalValueResolver(globalValueResolver);
            return this;
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.SetGlobalBooleanSubstitute<TBooleanSubstitute>()
        {
            return (this as IFluentMapperConfiguration).SetGlobalBooleanSubstitute(ObjectFactory<TBooleanSubstitute>.Create());
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.SetGlobalBooleanSubstitute(IBooleanSubstitute booleanSubstitute)
        {
            new MappingExpression<object, object>(null).SetGlobalBooleanSubstitute(booleanSubstitute);
            return this;
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.SetGlobalNullSubstitute<TNullSubstitute>()
        {
            return (this as IFluentMapperConfiguration).SetGlobalNullSubstitute(ObjectFactory<TNullSubstitute>.Create());
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.SetGlobalNullSubstitute(INullSubstitute nullSubstitute)
        {
            new MappingExpression<object, object>(null).SetGlobalNullSubstitute(nullSubstitute);
            return this;
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.SetGlobalNullAlias<TNullAlias>()
        {
            return (this as IFluentMapperConfiguration).SetGlobalNullAlias(ObjectFactory<TNullAlias>.Create());
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.SetGlobalNullAlias(INullAlias nullAlias)
        {
            new MappingExpression<object, object>(null).SetGlobalNullAlias(nullAlias);
            return this;
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.SetCacheMechanism(ITinyCache<string> tinyCache)
        {
            TinyCache = tinyCache;
            return this;
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.FallBackToLinqExpressionBuilder(bool shouldFallBackToLinqExpressionBuilder)
        {
            ShouldFallBackToLinqExpressionBuilder = shouldFallBackToLinqExpressionBuilder;
            return this;
        }

        IFluentMapperConfiguration IFluentMapperConfiguration.SetDefaultCulture(CultureInfo cultureInfo)
        {
            DefaultCulture = cultureInfo;
            return this;
        }

        public IFluentORMIntegrationConfiguration StartORMIntegration<TORMIntegration>()
            where TORMIntegration : class, IORMIntegration, new()
        {
            var ctor = ObjectFactory.GetConstructor<TORMIntegration>();

            var ctorExpression = Expression.New(ctor);

            var func = Expression.Lambda<Func<TORMIntegration>>(ctorExpression).Compile();

            return StartORMIntegration(func);
        }

        public IFluentORMIntegrationConfiguration StartORMIntegration<TORMIntegration>(Func<TORMIntegration> tormIntegrationFactory)
            where TORMIntegration : class, IORMIntegration
        {
            ORMIntegrationFactory = tormIntegrationFactory;

            return new FluentORMIntegrationConfiguration();
        }

        public IFluentORMIntegrationConfiguration StartORMIntegration(IORMIntegration ormIntegration)
        {
            return StartORMIntegration(() => ormIntegration);
        }
    }

    internal class AcceptNullValuesWhenFilteringType
    {
        private readonly Type _targetType;
        private readonly bool _acceptNullValuesWhenFiltering;

        public AcceptNullValuesWhenFilteringType(Type targetType, bool acceptNullValuesWhenFiltering)
        {
            _targetType = targetType;
            _acceptNullValuesWhenFiltering = acceptNullValuesWhenFiltering;
        }

        public bool TestType<TEntity>()
        {
            return _targetType == typeof(TEntity) && _acceptNullValuesWhenFiltering;
        }
    }
}